Introduction
List
Tuple
Reference
One more topic you'll need to understand is the list
data type and its cousin, the tuple
. Lists
and tuples
can contain multiple values, which makes writing programs that handle large amounts of data easier.
These data types are called containers, meaning they are objects that "contain" other objects. They each have some important distinguishing properties and come with their own set of function called methods for interacting with others.
List
and tuple
belong to sequence data types, which means they represent ordered collections of items. They share the same characteristic as string
and the range
object returned by range()
function.
In a string
, the values are characters; in a list
, they can be any type. The values in a list
are called elements or sometimes items. Items are separated with commas.
There are several ways to create a new list
; the simplest is to enclose the elements in square brackets (“[” and ”]”). A list
that contains no elements is called an empty list
; you can create one with empty brackets.
type([])
list
type([10, 20, 30, 40]), type(['calculus', 'introduction to mathematics', 'computer programming', 'linear algebra'])
(list, list)
The first example is a list of four integers and the second is a list of four strings.
You can reference a list
item by writing the list
’s name followed by the element’s index (that is, its position number) enclosed in square brackets ([]
, known as the subscript operator or bracket operator). Remember that the indices start at 0:
subjects = ['calculus', 'introduction to mathematics', 'computer programming', 'linear algebra']
print(subjects[0])
print(subjects[3])
calculus linear algebra
Note that the first index is 0, the last index is one less than the size of the list
; a list
of four items has 3 as its last index.
Python will give you an IndexError
error message (which is a type of runtime error) if you use an index that exceeds the number of values in your list value.
print(subjects[4])
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) ~\AppData\Local\Temp\ipykernel_5960\2337914966.py in <module> ----> 1 print(subjects[4]) IndexError: list index out of range
The elements of a list
don’t have to be the same type. The following list
contains a string
, a float
, an integer
, and another list
:
spam = ['spam', 2.0, 5, [10, 20]]
The values in these lists of lists can be accessed using multiple indexes:
spam[3][1] # spam = ['spam', 2.0, 5, [10, 20]]
20
The first index dictates which items in the outer list
to use, and the second indicates the value within the inner list
. If you only use one index like spam[3]
, the program will print the entire list value at that index.
spam[3]
[10, 20]
display_quiz(path+"list1.json", max_width=800)
display_quiz(path+"list2.json", max_width=800)
len()
function¶While indexes start at 0 and go up, you can also use negative integers for the index. The integer value -1 refers to the last index in a list
, the value -2 refers to the second-to-last index in a list
, and so on.
print(subjects[-1]) # subjects = ['calculus', 'introduction to mathematics', 'computer programming', 'linear algebra']
print(subjects[-2])
linear algebra computer programming
The len()
function will return the number of values that are in a list
, just like it can count the number of characters in a string.
len(subjects)
4
List
with Slices¶Just as an index can get a single value from a list
, a slice can get several values from a list
as a new list. A slice is typed between square brackets, like an index, but has two integers separated by a colon.
subjects[2]
is a list with an index.subjects[1:3]
is a list with a slice.The slice operator [n:m]
returns the part of the string starting with the element at index n
and go up to but not including the element at index m
. A slice evaluates to a new list
!
subjects = ['calculus', 'introduction to mathematics', 'computer programming', 'linear algebra']
print(subjects[0:3])
print(subjects[1:-1])
['calculus', 'introduction to mathematics', 'computer programming'] ['introduction to mathematics', 'computer programming']
As a shortcut, you can leave out one or both indexes on either side of the colon in the slice. Leaving out the first index is the same as using 0 or the beginning of the list
. Leaving out the second index is the same as using the length of the list
, which will slice to the end of the list
.
# subjects = ['calculus', 'introduction to mathematics', 'computer programming', 'linear algebra']
print(subjects[:3]) # same as subjects[0:3]
print(subjects[1:]) # same as subjects[1:len(s)]
print(subjects[:]) # same as s[0:len(s)]
['calculus', 'introduction to mathematics', 'computer programming'] ['introduction to mathematics', 'computer programming', 'linear algebra'] ['calculus', 'introduction to mathematics', 'computer programming', 'linear algebra']
Just like range()
, slicing has the optional third index that can be used to specify the step.
# subjects = ['calculus', 'introduction to mathematics', 'computer programming', 'linear algebra']
print(subjects[::2]) # Note the default step is 1
print(subjects[::-1]) # Reverse the order of the list, subjects[len(subjects):0:-1]
['calculus', 'computer programming'] ['linear algebra', 'computer programming', 'introduction to mathematics', 'calculus']
display_quiz(path+"slice.json", max_width=800)
List
with Indexes¶Unlike strings
, lists
are mutable because you can reassign an item in a list
. When the bracket operator appears on the left side of an assignment, it identifies the element of the list
that will be assigned. An assignment to an element of a list is called item assignment:
numbers = [17, 123, 42, 7]
numbers[1] = 5
print(numbers)
[17, 5, 42, 7]
The first element of numbers, which used to be 123, is now 5.
List
Concatenation and List
Replication¶Lists can be concatenated and replicated just like strings. The +
operator combines two lists to create a new list
and the *
operator can be used with a list
and an integer value to replicate the list
.
[1, 2, 3] + ['A', 'B', 'C']
[1, 2, 3, 'A', 'B', 'C']
['X', 'Y', 'Z'] * 3
['X', 'Y', 'Z', 'X', 'Y', 'Z', 'X', 'Y', 'Z']
display_quiz(path+"concate.json", max_width=800)
del
Statements¶The del
statement will delete values at an index in a list
. All values in the list
after the deleted value will be moved up to the front of list.
t = ['a', 'b', 'c', 'd', 'e']
del t[1] # using index
print(t)
['a', 'c', 'd', 'e']
We can also delete multiple adjacent elements using slicing:
del t[1:3]
print(t)
['a', 'e']
List
traversal¶In Chapter 2, you have learned about using for
loops to execute a block of code a certain number of times. Technically, a for
loop repeats the code block once for each item in a sequence. We will refer to this type of sequence iteration as iteration by item.
for i in range(4):
print(i)
0 1 2 3
print(range(4))
list(range(4))
range(0, 4)
[0, 1, 2, 3]
This is because the return value from range(4)
is a sequence that Python considers similar to [0, 1, 2, 3]
. The following program has the same output as the previous one:
for i in [0, 1, 2, 3]:
print(i)
0 1 2 3
for subject in subjects: # subjects = ['calculus', 'introduction to mathematics', 'computer programming', 'linear algebra']
print(subject)
calculus introduction to mathematics computer programming linear algebra
This works well if you only need to read the elements of the list
. But you need the indices if you want to write or update the elements. A common way to do that is to combine the functions range()
and len()
:
A common Python
technique is to use range(len(someList))
with a for
loop to iterate over the indexes of a list.
numbers = [17, 5, 42, 7]
for i in range(len(numbers)):
print(i, numbers[i])
numbers[i] = numbers[i]**2
print(numbers)
0 17 1 5 2 42 3 7 [289, 25, 1764, 49]
in
and not in
Operators¶You can determine whether an object is or isn’t in a list
with the in
and not in
operators. These expressions will evaluate to a Boolean
value.
print('howdy' in ['hello', 'hi', 'howdy', 'heyas'])
print('English' not in subjects)
True True
enumerate()
Function with Lists¶Instead of using the range(len(someList))
technique with a for
loop to obtain the integer index of the items in the list
, you can call the enumerate()
function instead. On each iteration of the loop, enumerate()
will return two values: the index of the item and the item itself.
numbers = [17, 5, 42, 7]
print(list(enumerate(numbers)))
for i, number in enumerate(numbers):
print(i, number)
numbers[i] = number**2
print(numbers)
[(0, 17), (1, 5), (2, 42), (3, 7)] 0 17 1 5 2 42 3 7 [289, 25, 1764, 49]
list
¶A method, introduced in Chapter 1, is the same as a function, except it is "called on" an object. For example, if a list
object were stored in spam
, you would call the index()
list method on that list
like so: spam.index('hello')
. The method part comes after the object, separated by a period.
Each data type has its own set of methods. The list
data type, for example, has several useful methods for finding, adding, removing, and otherwise manipulating values in a list
.
Lists
with the append()
and insert()
Methods¶append()
adds a new element to the end of a list
:
t = ['a', 'b', 'c']
t.append('d') # not t = t.append('d')
t # in-place operation!
['a', 'b', 'c', 'd']
The previous append()
method call adds the argument to the end of the list
. The insert()
method can insert an element at any index in the list
. The first argument to insert()
is the index for the new value, and the second argument is the new value to be inserted.
t = ['a', 'b', 'c']
t.insert(1,'e') # not t = t.insert(1, 'e')
t # in-place operation!
['a', 'e', 'b', 'c']
Methods belong to a single data type. The append()
and insert()
methods are list
methods and can be called only on list
object, not on other objects such as strings
or integers
.
eggs = 'hello'
eggs.append('world')
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) ~\AppData\Local\Temp\ipykernel_5960\3759910952.py in <module> 1 eggs = 'hello' ----> 2 eggs.append('world') AttributeError: 'str' object has no attribute 'append'
List
to the end of List
with the extend()
Methods¶Use list
method extend()
to add all the elements of another sequence to the end of a list:
color_names = ['orange', 'yellow', 'green']
color_names.extend(['indigo', 'violet']) # equivalent to color_names += ['indigo', 'violet']
color_names
['orange', 'yellow', 'green', 'indigo', 'violet']
display_quiz(path+"append.json", max_width=800)
Lists
with the remove()
Method¶The remove()
method will pass the object to be removed from the list
when it is called:
spam = ['cat', 'bat', 'rat', 'elephant']
spam.remove('bat')
print(spam)
['cat', 'rat', 'elephant']
display_quiz(path+"remove.json", max_width=800)
List
with the sort()
Method¶Lists of numbers or lists of strings can be sorted with the sort()
method:
spam = [2, 5, 3.14, 1, -7]
spam.sort() # The default behavior is sorting in ascending order
print(spam)
spam = ['ants', 'cats', 'dogs', 'badgers', 'Elephants']
spam.sort()
print(spam)
[-7, 1, 2, 3.14, 5] ['Elephants', 'ants', 'badgers', 'cats', 'dogs']
Note that sort()
uses “ASCII order” rather than alphabetical order for sorting strings. This means uppercase letters come before lowercase letters. Therefore, the lowercase a is sorted so that it comes after the uppercase Z.
You can also pass True
for the reverse
keyword argument to have sort()
sort the values in reverse order.
spam.sort(reverse=True) # Sort in descending order
print(spam)
['dogs', 'cats', 'badgers', 'ants', 'Elephants']
List
with the index()
Method¶List
objects have an index()
method that accepts an argument, and if that argument exists in the list, the index of the argument is returned. If the argument isn’t in the list
, then Python
produces a ValueError
error.
spam = ['hello', 'hi', 'howdy', 'heyas']
spam.index('hi')
1
spam = ['hello', 'hi', 'howdy', 'heyas']
spam.index('world')
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) ~\AppData\Local\Temp\ipykernel_5960\4200790458.py in <module> 1 spam = ['hello', 'hi', 'howdy', 'heyas'] ----> 2 spam.index('world') ValueError: 'world' is not in list
When there are duplicates of the elements in the list
, the index of its first appearance is returned.
spam = ['Zophie', 'Pooka', 'Fat-tail', 'Pooka']
spam.index('Pooka')
1
list
¶There are a number of built-in functions that can be used on lists
that allow you to quickly look through a list
without writing your own loops:
nums = [3, 41, 12, 9, 74, 15]
print(len(nums))
6
print(max(nums))
print(min(nums))
print(sum(nums))
74 3 154
List
Comprehensions¶Consider how you might make a list
of the first 10 square numbers (that is, the square of each integer from 1 through 10).
squares = []
for value in range(1,11):
squares.append(value**2)
print(squares)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
But a list comprehension allows you to generate this same list in just one line of code. A list comprehension combines the for
loop and the creation of new elements into one line, and automatically appends each new element!
squares = [value**2 for value in range(1, 11)]
print(squares)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
To use this syntax
list
, such as squares
.list
. In this example, the expression is value**2
for
loop to generate the numbers you want to feed into the expression and close the square brackets. In this example, the for
loop iterates value in range(1, 11)
, which feeds the values 1 through 10 into the expression value**2
.Note that no colon is used at the end of the for
statement.
Another common operation is filtering elements to select only those that match a condition. This typically produces a list
with fewer elements than the data being filtered. To do this in a list
comprehension, use the if
clause. The following
includes in list1
only the even values produced by the for
clause:
list1 = [item for item in range(1, 11) if item % 2 == 0]
list1
[2, 4, 6, 8, 10]
Exercise 1: In this exercise, you will implement the Bulls and Cows game where the computer generates a random 4-digit secret number with distinct digits, and the player tries to guess it; for each guess, the program compares the input to the secret number and returns a result in the format “XAXB,” where each “A” indicates a digit that is both correct and in the right position (a bull) and each “B” indicates a correct digit in the wrong position (a cow)—for instance, if the secret is 4271 and the guess is 1234, the output should be “1A2B” because the digit “2” is correctly placed while “4” and “1” are present but misplaced.¶
import random
# Generate a random four-digit number
def generate_number():
digits = list(range(10))
random.shuffle(digits) # randomly shuffle the list!
return digits[:4]
# Check the user's guess against the secret number
def check_guess(guess, secret):
# Note that both guess and secret are lists!
a = 0 # number of correct digits in the correct position
b = 0 # number of correct digits in the wrong position
for _________________: # iterate over list and get the index
if guess[i] == secret[i]:
a += 1
elif __________: # Use operator to determine whether the digit is in secret number or not
b += 1
return a, b
# Play the game
print("Welcome to 1A2B!")
print("I'm thinking of a four-digit number. Can you guess it?")
secret = generate_number()
guesses = 0
while True:
guess = input("Enter your guess, enter 'quit' to give up: ")
if guess == 'quit':
print("The secret number is", secret)
break
elif len(guess) != 4 or not guess.isdigit():
print("Invalid guess. Please enter a four-digit number.")
continue
guess = _______ # Use list comprehension to get the 4-digit guess list
guesses += 1
result = check_guess(guess, secret)
print(result[0],'A', result[1], 'B', sep="")
if result[0] == 4:
print("Congratulations, you guessed the number in", guesses, "guesses!")
break
Lists
aren’t the only data types that represent ordered sequences of values. For example, strings
and lists
are similar if you consider a string to be a "list" of single text characters.
The Python
sequence data types include lists
, strings
, range objects returned by range()
, and tuples
. Many of the things you can do with lists
can also be done with strings
and other values of sequence types: indexing; slicing; and using them with for loops, with len()
, and with the in
and not in
operators.
'a' in 'apple'
True
But lists
and strings
are different in an important way. A list object is a mutable data type: it can have elements added, removed, or changed. However, a string is immutable: it cannot be changed. Trying to reassign a single character in a string results in a TypeError
error:
name = 'Zophie a cat'
name[7] = 't'
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) ~\AppData\Local\Temp\ipykernel_5960\2958416556.py in <module> 1 name = 'Zophie a cat' ----> 2 name[7] = 't' TypeError: 'str' object does not support item assignment
A tuple
is a sequence of values much like a list
. The values stored in a tuple
can be any type, and they are indexed by integers. The important difference is that tuples
are immutable.
Although it is not necessary, it is common to enclose tuples
in parentheses to help us quickly identify tuples
when we look at Python
code:
type(())
tuple
t = ('a', 'b', 'c', 'd', 'e')
type(t)
tuple
To create a tuple
with a single element, you have to include the final comma or use the tuple()
function:
t1 = ('a',)
t2 = tuple('a')
print(type(t1), type(t2))
t3 = ('a')
print(type(t3))
print(t1, t2, t3)
<class 'tuple'> <class 'tuple'> <class 'str'> ('a',) ('a',) a
If the argument of tuple()
is a sequence (string
, list
, or tuple
), the result is a tuple
with the elements of the sequence:
t = tuple('nsysu')
t
('n', 's', 'y', 's', 'u')
Most list
operators also work on tuples
. The bracket operator indexes an element:
print(t[0]) # t = tuple('nsysu')
print(t[1:3])
n ('s', 'y')
But if you try to modify one of the elements of the tuple
, you get an error:
t[0] = 'A'
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) ~\AppData\Local\Temp\ipykernel_5960\3054271853.py in <module> ----> 1 t[0] = 'A' TypeError: 'tuple' object does not support item assignment
You can use tuples
to convey to anyone reading your code that you don’t intend for that sequence of values to change. Use a tuple
if you need an ordered sequence of values that never changes.
display_quiz(path+"tuple.json", max_width=800)
We have seen the multiple assignment trick in the previous chapter (which is actually unpacking the tuple
). In fact, you can unpack any sequence’s elements by assigning the sequence to a comma-separated list of variables.
student_tuple = ('Amanda', [98, 85, 87])
first_name, grades = student_tuple
print(first_name, grades)
Amanda [98, 85, 87]
Unpacking is widely used to return multiple values in a function:
def total_ave(grade):
total = sum(grade)
ave = total/len(grade)
return total, ave
grades = [85, 70, 100, 90]
total, ave = total_ave(grades)
print(total, ave)
345 86.25
Technically, in Python
, variables store references to the computer memory locations where the values are stored.
spam = 42
cheese = spam
print(id(cheese), id(spam))
spam = 100
print(id(cheese), id(spam))
spam, cheese
1951386922576 1951386922576 1951386922576 1951387112912
(100, 42)
The identifier returned by id()
is actually the memory address of the object, represented as a Python integer. All values in Python have a unique identity (address) that can be obtained with the id()
function.
But lists
don’t work this way, because list
are mutable:
spam = [0, 1, 2, 3, 4, 5]
cheese = spam # The reference is being copied, not the list.
print(id(cheese), id(spam))
cheese[1] = 'Hello!' # This changes the list value!
print(id(cheese), id(spam))
spam, cheese
1951470862016 1951470862016 1951470862016 1951470862016
([0, 'Hello!', 2, 3, 4, 5], [0, 'Hello!', 2, 3, 4, 5])
Using boxes as a metaphor for variables, the following shows what happens when a list
is assigned to the spam
variable.
Then, the reference in spam
is copied to cheese
. Only a new reference was created and stored in cheese
, not a new list
. Note how both references refer to the same list
.
When you alter the list
that cheese
refers to, the list
that spam
refers to is also changed, because both cheese
and spam
refer to the same list
.
Like integer
, 'Hello'
is a string
which is immutable and cannot be changed. If you "change" the string
in a variable, a new string
object is being made at a different place in memory, and the variable refers to this new string
.
bacon = 'Hello'
print(id(bacon))
bacon = bacon + 'World'
print(id(bacon))
1951470839536 1951470840624
However, lists
can be modified because they are mutable objects. The append()
method doesn’t create a new list
object; it changes the existing list
object. We call this "modifying the object in-place."
eggs = ['Hello'] # This creates a new list.
print(id(eggs))
eggs.append('World') # append() modifies the list "in place".
print(id(eggs)) # eggs still refers to the same list as before.
1951470861376 1951470861376
If two variables refer to the same list
(like spam
and cheese
in the previous section) and the list
itself changes, both variables are affected because they both refer to the same list
. The append()
, remove()
, sort()
, and other list
methods modify their lists
in place.
References are particularly important for understanding how arguments get passed to functions. When a function is called, the values of the arguments are copied to the parameter variables.
For lists
(and dictionaries
, which we will describe in the next chapter), this means a copy of the reference is used for the parameter.
def eggs(someParameter):
someParameter.append('Hello')
spam = [1, 2, 3]
eggs(spam)
print(spam)
[1, 2, 3, 'Hello']
Notice that when eggs()
is called, a return value is not used to assign a new value to spam
. Instead, it modifies the list in place directly. Even though spam
and someParameter
contain separate references, they both refer to the same list
.
For immutable types string
and integers
, we will create a new object in the function when we modify someParameter
. Therefore, the original value will not be modified after the loop.
def eggs(someParameter):
print(id(someParameter))
someParameter = someParameter + "world" # This will create a new object and assign the new reference to someParameter
print(id(someParameter))
spam = "hello"
print(id(spam))
eggs(spam)
print(spam)
1951465911152 1951465911152 1951470818352 hello
copy
Module’s copy()
and deepcopy()
Functions¶Python
provides a module named copy
that provides both the copy()
and deepcopy()
functions. copy()
, can be used to make a duplicate copy of a mutable value like a list or dictionary, not just a copy of a reference.
import copy
spam = ['A', 'B', 'C', 'D']
print(id(spam))
cheese = copy.copy(spam)
print(id(cheese)) # cheese is a different list with different identity.
cheese[1] = 42
spam, cheese
1951469880512 1951470840320
(['A', 'B', 'C', 'D'], ['A', 42, 'C', 'D'])
Now the spam
and cheese
variables refer to separate lists
, which is why only the list in cheese
is modified when you assign 42 at index 1.
Exercise 2: Here, we will simulate the process of a simple card game. The game is played with a standard deck of 52 cards, and we will randomly select 40 cards and divide them evenly between two players. Each player gets a hand of 20 cards. The goal of the game is to collect pairs of cards with the same rank (e.g., two aces, two kings, etc.). The player with the most pairs at the end of the game wins.¶
import random
# Write a function create_deck that creates a list of tuples representing a standard deck of 52 cards.
# Each tuple should contain two elements: the rank (e.g., "ace", "king", etc.)
# and the suit (e.g., "hearts", "spades", etc.).
def create_deck():
ranks = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
suits = ['♣', '♦', '♥', '♠']
deck = [(rank, suit) ______] # Use list comprehension to create the deck.
return deck
# A function that takes the deck as a parameter and returns two lists, each containing 26 randomly-selected
# cards from the deck. Use list slicing and the random module to implement this function.
def deal_cards(deck):
deck = deck[:40]
random.shuffle(deck)
hand1 = _____ # Split it into 20 cards in each using slice
hand2 = _____
return hand1, hand2
# Write a function find_pairs that takes a list of cards as a parameter and returns a list of tuples
# representing the pairs of cards in the list. A pair is defined as two cards with the same rank.
def find_pairs(cards):
pairs = []
for i, card1 in enumerate(cards):
for j, card2 in enumerate(cards):
if i != j and card1[0] == card2[0] and card1 not in [pair[0] for pair in pairs]\
and card1 not in [pair[1] for pair in pairs] and card2 not in [pair[0] for pair in pairs]\
and card2 not in [pair[1] for pair in pairs]:
pairs._____((card1, card2)) # Use a method from the list to add it into the pairs
return pairs
deck = create_deck()
hand1, hand2 = deal_cards(deck)
pairs1 = find_pairs(hand1)
pairs2 = find_pairs(hand2)
print(pairs1)
print(pairs2)
if ___________: # Compare the length of the two lists
print("Player 1 wins!")
elif _____________:
print("Player 2 wins!")
else:
print("It's a tie!")
from jupytercards import display_flashcards
fpath= "https://raw.githubusercontent.com/phonchi/nsysu-math106A/refs/heads/main/extra/flashcards/"
display_flashcards(fpath + 'ch4.json')